﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using System.Xml.Linq;

namespace Objects.Factories
{
    /// <summary>
    /// Фабрика сериализуемых объектов
    /// </summary>
    /// <typeparam name="Tpar">Тип параметра фабричного метода (тип схемы сборки)</typeparam>
    /// <typeparam name="Tres">Тип возвращаемого результа фабричного метода (базовый тип собираемых объектов)</typeparam>
    /// <typeparam name="Tns">Тип идентификатора конвейера</typeparam>
    public class BaseFactory<Tns, Tpar, Tres>
    {
        public const string SECTION_ENTER_POINT = "EnterPoint";
        public const string SECTION_INHERIT = "Inherit";
        [Flags]
        protected enum PipelinePolicy
        {
            /// <summary>
            /// Действия по-умолчанию
            /// </summary>
            Default,
            /// <summary>
            /// Незаменямые секции (при попытке добавления нового делегата он отбрасывается)
            /// </summary>
            StableSection,
            /// <summary>
            /// Заменямые секции (при попытке добавления нового делегата старый затирается, новый встает на место старого)
            /// </summary>
            UnstableSectionAdd,
            /// <summary>
            /// Выполнение единственной секции
            /// </summary>
            OneSection
        }

        /// <summary>
        /// Метод сборщик
        /// </summary>
        /// <param name="carry">Носитель заготовки</param>
        /// <param name="prms">Параметры сборки</param>
        public delegate void AssemblyMethod(Carry<Tres, Tns> carry, Tpar prms);
        public delegate void ParametrAssemblyMethod<Tsectprm>(Carry<Tres, Tns> carry, Tpar prms, Tsectprm sectPrms);

        public class Section
        {
            public string Name;
            protected Section()
            {
            }
            public Section(string Name, AssemblyMethod Method)
                : this()
            {
                this.Name = Name;
                this.Method = Method;
            }
            public AssemblyMethod Method;
            public virtual void Invoke(Carry<Tres, Tns> carry, Tpar prms)
            {
                if (Method != null)
                    Method(carry, prms);
            }

            #region ICloneable Members
            public virtual Section Clone()
            {
                return new Section(Name, Method);
            }
            #endregion
        }

        public class Section<Tsecprm> : Section
        {
            public ParametrAssemblyMethod<Tsecprm> ParaMethod;
            public Section(ParametrAssemblyMethod<Tsecprm> ParaMethod)
            {
                this.ParaMethod = ParaMethod;
            }
            public Tsecprm Parameters;
            public override Section Clone()
            {
                Section<Tsecprm> res = new Section<Tsecprm>(ParaMethod);
                res.Parameters = Parameters;
                return res;
            }
            public override void Invoke(Carry<Tres, Tns> carry, Tpar prms)
            {
                ParaMethod(carry, prms, Parameters);
            }
        }

        protected class Pipeline
        {
            public Tns Name;
            private PipelinePolicy _Policy;
            /// <summary>
            /// Политика действий
            /// </summary>        
            public PipelinePolicy Policy
            {
                get { return _Policy; }
                set
                {
                    _Policy = value;
                    if (IsPolicy(PipelinePolicy.OneSection))
                    {
                        Make = ExecOne;
                        MakeFrom = ExecOneFrom;
                        MakeFromWithSkip = ExecOneFromSkip;
                    }
                    else
                    {
                        Make = ExecOrder;
                        MakeFrom = ExecOrderFrom;
                        MakeFromWithSkip = ExecOrderFromSkip;
                    }
                }
            }

            private bool IsPolicy(PipelinePolicy policy)
            {
                return (Policy & policy) == policy;
            }

            public readonly Dictionary<string, Section> Sections = new Dictionary<string, Section>(StringComparer.CurrentCultureIgnoreCase);
            public void Add(string Name, AssemblyMethod Method)
            {
                Add(Name, new Section(Name, Method));
            }

            public void Add(string Name, Section section)
            {
                if (Sections.ContainsKey(Name))
                {
                    if (!IsPolicy(PipelinePolicy.StableSection))
                        if (IsPolicy(PipelinePolicy.UnstableSectionAdd))
                            Sections[Name] = section;
                        else
                        {
                            Sections.Remove(Name);
                            Sections.Add(Name, section);
                        }
                }
                else
                    Sections.Add(Name, section);
            }

            public Proc<Carry<Tres, Tns>, Tpar> Make;
            public Proc<string, Carry<Tres, Tns>, Tpar> MakeFrom;
            public Proc<string, Carry<Tres, Tns>, Tpar> MakeFromWithSkip;

            private void ExecOrder(Carry<Tres, Tns> carry, Tpar prms)
            {
                foreach (var item in Sections)
                {
                    carry.AddHistoryLine(Name, item.Key);
                    item.Value.Invoke(carry, prms);
                }
            }
            private void ExecOrderFrom(string asmBegin, Carry<Tres, Tns> carry, Tpar prms)
            {
                foreach (var item in Sections.SkipWhile(s => s.Key != asmBegin))
                {
                    carry.AddHistoryLine(Name, item.Key);
                    item.Value.Invoke(carry, prms);
                }
            }
            private void ExecOrderFromSkip(string asmBegin, Carry<Tres, Tns> carry, Tpar prms)
            {
                foreach (var item in Sections
                    .SkipWhile(s => s.Key != asmBegin)
                    .Skip(1))
                {
                    carry.AddHistoryLine(Name, item.Key);
                    item.Value.Invoke(carry, prms);
                }
            }
            private void ExecOne(Carry<Tres, Tns> carry, Tpar prms)
            {
                var item = Sections.FirstOrDefault();
                if (item.Value != null)
                {
                    carry.AddHistoryLine(Name, item.Key);
                    item.Value.Invoke(carry, prms);
                }
            }
            private void ExecOneFrom(string asmBegin, Carry<Tres, Tns> carry, Tpar prms)
            {
                var item = Sections.FirstOrDefault(s => s.Key == asmBegin);
                if (item.Value != null)
                {
                    carry.AddHistoryLine(Name, item.Key);
                    item.Value.Invoke(carry, prms);
                }
            }
            private void ExecOneFromSkip(string asmBegin, Carry<Tres, Tns> carry, Tpar prms)
            {
                var item = Sections.SkipWhile(s => s.Key != asmBegin)
                    .Skip(1)
                    .FirstOrDefault();
                if (item.Value != null)
                {
                    carry.AddHistoryLine(Name, item.Key);
                    item.Value.Invoke(carry, prms);
                }
            }

            public Pipeline(Tns Name)
            {
                this.Name = Name;
            }
            public Pipeline(Tns Name, PipelinePolicy policy)
            {
                this.Name = Name;
                this.Policy = policy;
            }
        }

        /// <summary>
        /// Запись прохода по секциямв носитель
        /// </summary>
        public bool IsWriteHistoryToCarry = true;

        protected PipelinePolicy DefaultPipelinePolicy = PipelinePolicy.UnstableSectionAdd;
        protected SynchronizedDictionary<Tns, Pipeline> pipelines = new SynchronizedDictionary<Tns, Pipeline>();
        protected SynchronizedDictionary<Tns, Tpar> defaultParametrs = new SynchronizedDictionary<Tns, Tpar>();

        /// <summary>
        /// Добавить фабричный метод в конвейер
        /// </summary>
        /// <param name="convId">Идентификатор конвейера</param>
        /// <param name="asmMethod">Сборочный метод</param>   
        /// <param name="asmName">Наименование сборщика</param>
        public virtual void AddAssemblyMethod(Tns convId, string asmName, AssemblyMethod asmMethod)
        {
            if (!convId.IsEquals(default(Tns)))
            {
                if (pipelines.ContainsKey(convId))
                    pipelines[convId].Add(asmName, asmMethod);
                else
                {
                    Pipeline pipe = new Pipeline(convId) { Policy = DefaultPipelinePolicy };
                    pipe.Add(asmName, asmMethod);
                    pipelines.Add(convId, pipe);
                }
            }
        }

        /// <summary>
        /// Добавить секцию в конвейер
        /// </summary>
        /// <param name="convId">Идентификатор конвейера</param>
        /// <param name="asmSection">Сборочный метод</param>   
        /// <param name="asmName">Наименование сборщика</param>
        public virtual void AddAssemblySection(Tns convId, string asmName, Section asmSection)
        {
            if (!convId.IsEquals(default(Tns)) && asmSection != null)
            {
                if (pipelines.ContainsKey(convId))
                    pipelines[convId].Add(asmName, asmSection);
                else
                {
                    Pipeline pipe = new Pipeline(convId) { Policy = DefaultPipelinePolicy };
                    pipe.Add(asmName, asmSection);
                    pipelines.Add(convId, pipe);
                }
            }
        }

        /// <summary>
        /// Добавить секцию в конвейер
        /// </summary>
        /// <param name="convId">Идентификатор конвейера</param>
        /// <param name="asmSection">Сборочный метод</param>   
        /// <param name="asmName">Наименование сборщика</param>
        public virtual void AddAssemblySection<Tsectprm>(Tns convId, string asmName, ParametrAssemblyMethod<Tsectprm> prmAsmMethod)
        {
            if (!convId.IsEquals(default(Tns)) && prmAsmMethod != null)
            {
                if (pipelines.ContainsKey(convId))
                    pipelines[convId].Add(asmName, new Section<Tsectprm>(prmAsmMethod));
                else
                {
                    Pipeline pipe = new Pipeline(convId) { Policy = DefaultPipelinePolicy };
                    pipe.Add(asmName, new Section<Tsectprm>(prmAsmMethod));
                    pipelines.Add(convId, pipe);
                }
            }
        }

        /// <summary>
        /// Перенаправление сборки в другой конвейер с возвратом (поддержка наследования классов)
        /// </summary>
        /// <param name="FromconvId">Исходный конвейер</param>
        /// <param name="FromasmName">Точка перенаправления (добавляется)</param>
        /// <param name="ToconvId">Конвейер адресат</param>
        /// <param name="ToasmName">Точка входа</param>
        public virtual void ForwardSection(Tns FromconvId, string FromasmName, Tns ToconvId, string ToasmName)
        {
            if (pipelines.ContainsKey(FromconvId))
                pipelines[FromconvId].Add(FromasmName,
                    (carry, prms) =>
                        pipelines[ToconvId].MakeFrom(ToasmName, carry, prms)
                    );
        }

        /// <summary>
        /// Перенаправление сборки в другой конвейер (пропуская одну секцию) с возвратом (поддержка наследования классов)
        /// </summary>
        /// <param name="FromconvId">Исходный конвейер</param>
        /// <param name="FromasmName">Точка перенаправления (добавляется)</param>
        /// <param name="ToconvId">Конвейер адресат</param>
        /// <param name="ToasmName">Точка входа</param>
        public virtual void ForwardSectionWithSkip(Tns FromconvId, string FromasmName, Tns ToconvId, string ToasmName)
        {
            if (pipelines.ContainsKey(FromconvId))
                pipelines[FromconvId].Add(FromasmName,
                    (carry, prms) =>
                            pipelines[ToconvId].MakeFromWithSkip(ToasmName, carry, prms)
                    );
        }

        /// <summary>
        /// Копирование сборочного метода из другого конвейера
        /// </summary>
        /// <param name="convId">Исходный конвейер</param>
        /// <param name="asmName">Имя сборки (добавляется)</param>
        /// <param name="SrcconvId">Конвейер источник</param>
        /// <param name="SrcasmName">Копируемая секция-сборщик</param>
        public virtual void CopySection(Tns convId, string asmName, Tns SrcconvId, string SrcasmName)
        {
            Section asm;
            if (pipelines.ContainsKey(convId) && pipelines.ContainsKey(SrcconvId) && pipelines[SrcconvId].Sections.TryGetValue(SrcasmName, out asm))
                pipelines[convId].Add(asmName, asm.Clone());
        }

        /// <summary>
        /// Копирование сборочного метода из другого конвейера
        /// </summary>
        /// <param name="convId">Исходный конвейер</param>
        /// <param name="asmName">Имя сборки (добавляется)</param>
        /// <param name="SrcconvId">Конвейер источник</param>
        /// <param name="SrcasmName">Копируемый метод-сборщик</param>
        public virtual void CopySection(Tns convId, Tns SrcconvId, string SrcasmName)
        {
            Section asm;
            if (pipelines.ContainsKey(convId) && pipelines.ContainsKey(SrcconvId) && pipelines[SrcconvId].Sections.TryGetValue(SrcasmName, out asm))
                pipelines[convId].Add(SrcasmName, asm.Clone());
        }

        public Tpar GetDefaultParametrs(Tns convId)
        {
            return defaultParametrs.GetOrDefault(convId, default(Tpar));
        }

        #region BuildMethods
        /// <summary>
        /// Собрать объект на основе идентификатора конвейера с последующей настройкой.
        /// </summary>
        /// <typeparam name="T">Совместимый тип</typeparam>
        /// <param name="convId">Идентификатор конвейера</param>
        /// <param name="prms">Параметры сборки</param>
        /// <returns></returns>
        public T Build<T>(Tns convId, Tpar prms)
            where T : class
        {
            return Make(convId, prms).TypeOf<T>();
        }

        /// <summary>
        /// 
        /// </summary>
        /// <typeparam name="T"></typeparam>
        /// <param name="convId"></param>
        /// <returns></returns>
        public T Build<T>(Tns convId)
            where T : class
        {
            return Make(convId, GetDefaultParametrs(convId)).TypeOf<T>();
        }

        public Carry<Tres, Tns> Make<T>(Tns convId)
            where T : class
        {
            return Make(convId, GetDefaultParametrs(convId));
        }

        /// <summary>
        /// Основной сборочный метод.
        /// </summary>
        /// <param name="convId">Идентификатор конвейера</param>
        /// <param name="prms">Параметры сборки</param>
        /// <returns>Носитель</returns>
        public virtual Carry<Tres, Tns> Make(Tns convId, Tpar prms)
        {
            Carry<Tres, Tns> res = new Carry<Tres, Tns>() { Content = default(Tres), IsWriteHistory = IsWriteHistoryToCarry };
            if (pipelines.ContainsKey(convId))
            {
                if (!IsWriteHistoryToCarry && pipelines[convId].Sections.Count > 0)
                {
                    res.StartLine.Element1 = convId;
                    res.StartLine.Element2 = pipelines[convId].Sections.First().Key;
                }
                pipelines[convId].Make(res, prms);
            }
            else
                res.Description = "Не найден конвейер сборки для {0}.".goFormat(convId);
            return res;
        }

        /// <summary>
        /// Cборочный метод с параметрам по-умолчанию.
        /// </summary>
        /// <param name="convId">Идентификатор конвейера</param>
        /// <returns>Носитель</returns>
        public Carry<Tres, Tns> Make(Tns convId)
        {
            return Make(convId, GetDefaultParametrs(convId));
        }

        /// <summary>
        /// Списочный сборочный метод с параметрам по-умолчанию.
        /// </summary>
        /// <param name="convsId">Идентификаторы конвейера</param>
        /// <returns>Носитель</returns>
        public IEnumerable<Carry<Tres, Tns>> Build(IEnumerable<Tns> convsId)
        {
            if (convsId != null)
                foreach (var item in convsId)
                    yield return Make(item);
        }

        /// <summary>
        /// Списочный сборочный метод.
        /// </summary>
        /// <param name="convsId">Идентификаторы конвейера</param>
        /// <param name="prms">Параметры</param>
        /// <returns>Носитель</returns>
        public IEnumerable<Carry<Tres, Tns>> Build(IEnumerable<Tns> convsId, IEnumerable<Tpar> prms)
        {
            if (convsId != null)
                if (prms == null)
                    foreach (var item in convsId)
                        yield return Make(item);
                else
                {
                    using (var cnv = convsId.GetEnumerator())
                    using (var prm = prms.GetEnumerator())
                        while (cnv.MoveNext())
                            if (prm.MoveNext())
                                yield return Make(cnv.Current, prm.Current);
                            else
                                yield return Make(cnv.Current);
                }
        }

        #endregion
    }

    /// <summary>
    /// Носитель заготовки
    /// </summary>
    /// <typeparam name="T">Тип заготовки</typeparam>
    public class Carry<T, Tns>
    {
        public OrderGroup<Tns, string> StartLine;
        private List<OrderGroup<Tns, string>> _History;
        /// <summary>
        /// История сборки (Имя линии, имя секции)
        /// </summary>        
        public List<OrderGroup<Tns, string>> History
        {
            get
            {
                if (_History == null)
                    _History = new List<OrderGroup<Tns, string>>();
                return _History;
            }
            private set { _History = value; }
        }
        public void AddHistoryLine(Tns Name, string section)
        {
            if (IsWriteHistory)
            {
                if (History.Count == 0)
                {
                    StartLine.Element1 = Name;
                    StartLine.Element2 = section;
                }
                History.Add(new OrderGroup<Tns, string>(Name, section));
            }
        }
        public bool IsWriteHistory;
        public string Description;
        public T Content = default(T);
        /// <summary>
        /// Приведение типа
        /// </summary>
        /// <typeparam name="TTarget">Тип назначения</typeparam>
        /// <returns></returns>
        public Carry<TTarget, Tns> Cast<TTarget>()
            where TTarget : T
        {
            return new Carry<TTarget, Tns>() { Description = Description, Content = (TTarget)Content, History = History };
        }
        /// <summary>
        /// Преобразование типа
        /// </summary>
        /// <typeparam name="TTarget">Тип назначения</typeparam>
        /// <returns></returns>
        public Carry<TTarget, Tns> Type<TTarget>()
            where TTarget : class
        {
            return new Carry<TTarget, Tns>() { Description = Description, Content = Content as TTarget, History = History };
        }
        public TTarget CastTo<TTarget>()
            where TTarget : T
        {
            return (TTarget)Content;
        }
        public TTarget TypeOf<TTarget>()
            where TTarget : class
        {
            return Content as TTarget;
        }
    }
}